{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Layout and Styling of Jupyter widgets\n",
    "\n",
    "This notebook presents how to layout and style Jupyter interactive widgets to build rich and *reactive* widget-based applications."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## The `layout` attribute.\n",
    "\n",
    "Jupyter interactive widgets have a `layout` attribute exposing a number of CSS properties that impact how widgets are laid out.\n",
    "\n",
    "### Exposed CSS properties\n",
    "\n",
    "<div class=\"alert alert-info\" style=\"margin: 20px\">\n",
    "The following properties map to the values of the CSS properties of the same name (underscores being replaced with dashes), applied to the top DOM elements of the corresponding widget.\n",
    "</div>\n",
    "\n",
    "\n",
    "#### Sizes\n",
    "\n",
    "- `height`\n",
    "- `width`\n",
    "- `max_height`\n",
    "- `max_width`\n",
    "- `min_height`\n",
    "- `min_width`\n",
    "\n",
    "#### Display\n",
    "\n",
    "- `visibility`\n",
    "- `display`\n",
    "- `overflow`\n",
    "- `overflow_x` (deprecated in `7.5`, use `overflow` instead)\n",
    "- `overflow_y` (deprecated in `7.5`, use `overflow` instead)\n",
    "\n",
    "#### Box model\n",
    "\n",
    "- `border` \n",
    "- `margin`\n",
    "- `padding`\n",
    "\n",
    "#### Positioning\n",
    "\n",
    "- `top`\n",
    "- `left`\n",
    "- `bottom`\n",
    "- `right`\n",
    "\n",
    "#### Flexbox\n",
    "\n",
    "- `order`\n",
    "- `flex_flow`\n",
    "- `align_items`\n",
    "- `flex`\n",
    "- `align_self`\n",
    "- `align_content`\n",
    "- `justify_content`\n",
    "\n",
    "#### Grid layout\n",
    "\n",
    "- `grid_auto_columns`\n",
    "- `grid_auto_flow`\n",
    "- `grid_auto_rows`\n",
    "- `grid_gap`\n",
    "- `grid_template`\n",
    "\n",
    "- `grid_row`\n",
    "- `grid_column`\n",
    "\n",
    "### Shorthand CSS properties\n",
    "\n",
    "You may have noticed that certain CSS properties such as `margin-[top/right/bottom/left]` seem to be missing. The same holds for `padding-[top/right/bottom/left]` etc.\n",
    "\n",
    "In fact, you can atomically specify `[top/right/bottom/left]` margins via the `margin` attribute alone by passing the string `'100px 150px 100px 80px'` for a respectively `top`, `right`, `bottom` and `left` margins of  `100`, `150`, `100` and `80` pixels.\n",
    "\n",
    "Similarly, the `flex` attribute can hold values for `flex-grow`, `flex-shrink` and `flex-basis`. The `border` attribute is a shorthand property for `border-width`, `border-style (required)`, and `border-color`."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Simple examples"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The following example shows how to resize a `Button` so that its views have a height of `80px` and a width of `50%` of the available space. It also includes an example of setting a CSS property that requires multiple values (a border, in thise case):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from ipywidgets import Button, Layout\n",
    "\n",
    "b = Button(description='(50% width, 80px height) button',\n",
    "           layout=Layout(width='50%', height='80px', border='2px dotted blue'))\n",
    "b"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The `layout` property can be shared between multiple widgets and assigned directly."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "Button(description='Another button with the same layout', layout=b.layout)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Description"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You may have noticed that long descriptions are truncated. This is because the description length is, by default, fixed."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from ipywidgets import IntSlider\n",
    "\n",
    "IntSlider(description='A too long description')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If you need more flexibility to lay out widgets and descriptions, you can use Label widgets directly."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from ipywidgets import HBox, Label\n",
    "\n",
    "HBox([Label('A too long description'), IntSlider()])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can change the length of the description to fit the description text. However, this will make the widget itself shorter. You can change both by adjusting the description width and the widget width using the widget's style."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "style = {'description_width': 'initial'}\n",
    "IntSlider(description='A too long description', style=style)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Natural sizes, and arrangements using HBox and VBox\n",
    "\n",
    "Most of the core-widgets have default heights and widths that tile well together. This allows simple layouts based on the `HBox` and `VBox` helper functions to align naturally:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from ipywidgets import Button, HBox, VBox\n",
    "\n",
    "words = ['correct', 'horse', 'battery', 'staple']\n",
    "items = [Button(description=w) for w in words]\n",
    "left_box = VBox([items[0], items[1]])\n",
    "right_box = VBox([items[2], items[3]])\n",
    "HBox([left_box, right_box])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### LaTeX"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Widgets such as sliders and text inputs have a description attribute that can render Latex Equations. The `Label` widget also renders Latex equations."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from ipywidgets import IntSlider, Label"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "IntSlider(description=r'\\(\\int_0^t f\\)')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "Label(value=r'\\(e=mc^2\\)')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Number formatting\n",
    "\n",
    "Sliders have a readout field which can be formatted using Python's [Format Specification Mini-Language](https://docs.python.org/3/library/string.html#format-specification-mini-language). If the space available for the readout is too narrow for the string representation of the slider value, a different styling is applied to show that not all digits are visible."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Four buttons in a VBox. Items stretch to the maximum width, in a vertical box taking `50%` of the available space.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from ipywidgets import Layout, Button, Box\n",
    "\n",
    "items_layout = Layout( width='auto')     # override the default width of the button to 'auto' to let the button grow\n",
    "\n",
    "box_layout = Layout(display='flex',\n",
    "                    flex_flow='column', \n",
    "                    align_items='stretch', \n",
    "                    border='solid',\n",
    "                    width='50%')\n",
    "\n",
    "words = ['correct', 'horse', 'battery', 'staple']\n",
    "items = [Button(description=word, layout=items_layout, button_style='danger') for word in words]\n",
    "box = Box(children=items, layout=box_layout)\n",
    "box"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Three buttons in an HBox. Items flex proportionally to their weight.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from ipywidgets import Layout, Button, Box, VBox\n",
    "\n",
    "# Items flex proportionally to the weight and the left over space around the text \n",
    "items_auto = [\n",
    "    Button(description='weight=1; auto', layout=Layout(flex='1 1 auto', width='auto'), button_style='danger'),\n",
    "    Button(description='weight=3; auto', layout=Layout(flex='3 1 auto', width='auto'), button_style='danger'),\n",
    "    Button(description='weight=1; auto', layout=Layout(flex='1 1 auto', width='auto'), button_style='danger'),\n",
    " ]\n",
    "\n",
    "# Items flex proportionally to the weight \n",
    "items_0 = [\n",
    "    Button(description='weight=1; 0%', layout=Layout(flex='1 1 0%', width='auto'), button_style='danger'),\n",
    "    Button(description='weight=3; 0%', layout=Layout(flex='3 1 0%', width='auto'), button_style='danger'),\n",
    "    Button(description='weight=1; 0%', layout=Layout(flex='1 1 0%', width='auto'), button_style='danger'),\n",
    " ]\n",
    "box_layout = Layout(display='flex',\n",
    "                    flex_flow='row', \n",
    "                    align_items='stretch', \n",
    "                    width='70%')\n",
    "box_auto = Box(children=items_auto, layout=box_layout)\n",
    "box_0 = Box(children=items_0, layout=box_layout)\n",
    "VBox([box_auto, box_0])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**A more advanced example: a reactive form.**\n",
    "\n",
    "The form is a `VBox` of width '50%'. Each row in the VBox is an HBox, that justifies the content with space between.."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from ipywidgets import Layout, Button, Box, FloatText, Textarea, Dropdown, Label, IntSlider\n",
    "\n",
    "form_item_layout = Layout(\n",
    "    display='flex',\n",
    "    flex_flow='row',\n",
    "    justify_content='space-between'\n",
    ")\n",
    "\n",
    "form_items = [\n",
    "    Box([Label(value='Age of the captain'), IntSlider(min=40, max=60)], layout=form_item_layout),\n",
    "    Box([Label(value='Egg style'), \n",
    "         Dropdown(options=['Scrambled', 'Sunny side up', 'Over easy'])], layout=form_item_layout),\n",
    "    Box([Label(value='Ship size'), \n",
    "         FloatText()], layout=form_item_layout),\n",
    "    Box([Label(value='Information'), \n",
    "         Textarea()], layout=form_item_layout)\n",
    "]\n",
    "\n",
    "form = Box(form_items, layout=Layout(\n",
    "    display='flex',\n",
    "    flex_flow='column',\n",
    "    border='solid 2px',\n",
    "    align_items='stretch',\n",
    "    width='50%'\n",
    "))\n",
    "form"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**A more advanced example: a carousel.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from ipywidgets import Layout, Button, Box, Label\n",
    "\n",
    "item_layout = Layout(height='100px', min_width='40px')\n",
    "items = [Button(layout=item_layout, description=str(i), button_style='warning') for i in range(40)]\n",
    "box_layout = Layout(overflow_x='scroll',\n",
    "                    border='3px solid black',\n",
    "                    width='500px',\n",
    "                    height='',\n",
    "                    flex_flow='row',\n",
    "                    display='flex')\n",
    "carousel = Box(children=items, layout=box_layout)\n",
    "VBox([Label('Scroll horizontally:'), carousel])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### *Compatibility note*\n",
    "\n",
    "The `overflow_x` and `overflow_y` options are deprecated in ipywidgets `7.5`. Instead, use the shorthand property `overflow='scroll hidden'`. The first part specificies overflow in `x`, the second the overflow in `y`."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## A widget for exploring layout options\n",
    "\n",
    "The widgets below was written by ipywidgets user [Doug Redden (@DougRzz)](https://github.com/DougRzz). If you want to look through the source code to see how it works, take a look at this [notebook he contributed](cssJupyterWidgetStyling-UI.ipynb).\n",
    "\n",
    "Use the dropdowns and sliders in the widget to change the layout of the box containing the five colored buttons. Many of the CSS layout optoins described above are available, and the Python code to generate a `Layout` object reflecting the settings is in a `TextArea` in the widget."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from layout_preview import layout\n",
    "layout"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "widgets-tutorial",
   "language": "python",
   "name": "widgets-tutorial"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
